using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.IO;
using System.Windows.Forms;
using UmlDesigner.Gui;

namespace UmlDesigner.Model
{
    [Serializable]
    abstract class ItemWidget
    {
        public ItemWidget()
        {
            leftSlots = new SlotManager(false, this);
            rightSlots = new SlotManager(false, this);
            topSlots = new SlotManager(true, this);
            bottomSlots = new SlotManager(true, this);
        }

        public Size ItemSize
        {
            get
            {
                return size;
            }
            set
            {
                size = value;
                RepositionSlots();
            }
        }

        public Point Position
        {
            get
            {
                return position;
            }
        }

        public Rectangle ClassRect
        {
            get
            {
                return new Rectangle(position, size);
            }
        }

        internal abstract bool IsPointWithin(Point p);

        internal abstract void PaintItem(Graphics e);

        internal abstract void PaintSelectionOutline(Graphics e);

        internal abstract bool ItemWithinArea(Rectangle rect);

        //public abstract ItemWidget Clone();

        public abstract void HandleDoubleClicked(UmlControl ctrl);

        public abstract void HandleFocusedOutline(Graphics g);

        public abstract void OnSetFocus();

        public abstract void OnKiilFocus();

        internal abstract void GenerateCode(CodeGenerationSettings settings);

        internal virtual bool IsNote()
        {
            return false;
        }

        internal virtual bool IsLink()
        {
            return false;
        }

        internal virtual bool IsClass()
        {
            return false;
        }

        internal virtual bool IsFolder()
        {
            return false;
        }

        public enum ItemSide { Left, Top, Right, Bottom };

        public ItemSide GetItemSide(Point destPoint)
        {
            if (Math.Abs(position.X - destPoint.X) > Math.Abs(position.Y - destPoint.Y))
            {
                // The connector will connect to one of the sides.
                if (position.X < destPoint.X)
                {
                    return ItemSide.Right;
                }
                else
                {
                    return ItemSide.Left;
                }
            }
            else
            {
                // The connector will be on the top or bottom.
                if (position.Y > destPoint.Y)
                {
                    return ItemSide.Top;
                }
                else
                {
                    return ItemSide.Bottom;
                }
            }
        }

        public virtual Point GetSrcPoint(Point destPoint)
        {
            switch (GetItemSide(destPoint))
            {
                case ItemSide.Bottom:
                    return new Point(position.X + ClassRect.Width / 2, position.Y + ClassRect.Height);

                case ItemSide.Top:
                    return new Point(position.X + ClassRect.Width / 2, position.Y);

                case ItemSide.Left:
                    return new Point(position.X, position.Y + ClassRect.Height / 2);

                case ItemSide.Right:
                    return new Point(position.X + ClassRect.Width, position.Y + ClassRect.Height / 2);

                default:
                    throw new Exception("Unknown side for connector");
            }
        }

        public virtual Point GetDestPoint(ItemWidget srcClass)
        {
            Point destPos;
            if (Math.Abs(srcClass.position.X - position.X) > Math.Abs(srcClass.position.Y - position.Y))
            {
                // The connector will connect to one of the sides.
                if (srcClass.position.X < position.X)
                {
                    destPos = new Point(position.X, position.Y + ClassRect.Height / 2);
                }
                else
                {
                    destPos = new Point(position.X + ClassRect.Width, position.Y + ClassRect.Height / 2);
                }
            }
            else
            {
                // The connector will be on the top or bottom.
                if (srcClass.position.Y > position.Y)
                {
                    destPos = new Point(position.X + ClassRect.Width / 2, position.Y + ClassRect.Height);
                }
                else
                {
                    destPos = new Point(position.X + ClassRect.Width / 2, position.Y);
                }
            }
            return destPos;
        }

        internal virtual void AddLinkToDest(LinkWidget link)
        {
            if (!links.Contains(link))
            {
                links.Add(link);

                RepositionSlots();
            }
        }

        internal virtual void RemoveLink(LinkWidget link)
        {
            links.Remove(link);

            leftSlots.DeleteSlot(link);
            rightSlots.DeleteSlot(link);
            topSlots.DeleteSlot(link);
            bottomSlots.DeleteSlot(link);

            RepositionSlots();
        }


        internal void AddLinkFromSource(LinkWidget link)
        {
            destLinks.Add(link);
        }

        internal virtual string GetClassName()
        {
            return "-Blank-";
        }

        internal virtual Point GetClassPosition()
        {
            return ClassRect.Location;
        }

        internal void SetClassPosition(Point pos)
        {
            position = pos;

            RepositionSlots();
        }

        protected virtual void RepositionSlots()
        {
            // Shuffle around the slots
            leftSlots.MovePosition(new Point(position.X, position.Y + ClassRect.Height / 2));
            rightSlots.MovePosition(new Point(position.X + ClassRect.Width, position.Y + ClassRect.Height / 2));
            topSlots.MovePosition(new Point(position.X + ClassRect.Width / 2, position.Y));
            bottomSlots.MovePosition(new Point(position.X + ClassRect.Width / 2, position.Y + ClassRect.Height));
        }

        public virtual Slot AssignSlot(LinkWidget linkWidget, Point point)
        {
            switch (GetItemSide(point))
            {
                case ItemSide.Bottom:
                    return bottomSlots.GetSlot(linkWidget);

                case ItemSide.Top:
                    return topSlots.GetSlot(linkWidget);

                case ItemSide.Left:
                    return leftSlots.GetSlot(linkWidget);

                case ItemSide.Right:
                    return rightSlots.GetSlot(linkWidget);

                default:
                    throw new Exception("Unknown side for connector");
            }
        }

        public virtual void FreeSlot(LinkWidget link)
        {
            bottomSlots.DeleteSlot(link);
            topSlots.DeleteSlot(link);
            leftSlots.DeleteSlot(link);
            rightSlots.DeleteSlot(link);
        }

        virtual internal void Dump(TreeNode parent)
        {
            TreeNode n = parent.Nodes.Add("Links: ");

            foreach (LinkWidget link in links)
            {
                n.Nodes.Add(link.GetHashCode().ToString());
            }
        }

        internal void RecalculateAllSlots()
        {
            RecalculateSlots();

            foreach (LinkWidget link in links)
            {
                if (link.GetSource() == this)
                {
                    link.GetDestination().RecalculateSlots();
                }
                else
                {
                    link.GetSource().RecalculateSlots();
                }
            }

            foreach (LinkWidget link in destLinks)
            {
                if (link.GetSource() == this)
                {
                    link.GetDestination().RecalculateSlots();
                }
                else
                {
                    link.GetSource().RecalculateSlots();
                }
            }

        }

        internal void RecalculateSlots()
        {
            RecombobulateSlots(topSlots);
            RecombobulateSlots(leftSlots);
            RecombobulateSlots(bottomSlots);
            RecombobulateSlots(rightSlots);
        }

        private void RecombobulateSlots(SlotManager mgr)
        {
            List<Slot> slots = mgr.GetAllSlots();

            foreach (Slot s in slots)
            {
                // Ignore any slots which are circular references
                if (s.Link.GetSource() == s.Link.GetDestination())
                {
                    continue;
                }

                Point pt;
                if (s.Link.GetSource() == this)
                {
                    pt = s.Link.GetDestination().GetClassPosition();
                }
                else
                {
                    pt = s.Link.GetSource().GetClassPosition();
                }

                mgr.DeleteSlot(s.Link);

                switch (GetItemSide(pt))
                {
                    case ItemSide.Bottom:
                        bottomSlots.AddSlot(s);
                        break;

                    case ItemSide.Top:
                        topSlots.AddSlot(s);
                        break;

                    case ItemSide.Left:
                        leftSlots.AddSlot(s);
                        break;

                    case ItemSide.Right:
                        rightSlots.AddSlot(s);
                        break;

                    default:
                        throw new Exception("Unknown side for connector");
                }
            }
        }

        internal List<LinkWidget> GetAllLinks()
        {
            return new List<LinkWidget>(links);
        }

        internal void RemoveDestLink(LinkWidget link)
        {
            destLinks.Remove(link);
        }

        internal void ClearLinksAndSlots()
        {
            links.Clear();
            destLinks.Clear();

            topSlots.ClearAllSlots();
            bottomSlots.ClearAllSlots();
            leftSlots.ClearAllSlots();
            rightSlots.ClearAllSlots();
        }

        /// <summary>
        /// Takes a shallow duplicate of the ItemWidget. Should only be used 
        /// by descendants of ItemWidget to copy the base class elements.
        /// </summary>
        /// <param name="target"></param>
        protected void ShallowCopyItemWidget(ItemWidget target)
        {
            target.position = position;
            target.size = size;

            target.links = new List<LinkWidget>(links);
            target.destLinks = new List<LinkWidget>(links);

            leftSlots.ShallowCopy(target.leftSlots);
            rightSlots.ShallowCopy(target.rightSlots);
            topSlots.ShallowCopy(target.topSlots);
            bottomSlots.ShallowCopy(target.bottomSlots);
        }

        private Point position = new Point();
        protected Size size = new Size(80, 120);

        protected List<LinkWidget> links = new List<LinkWidget>();
        protected List<LinkWidget> destLinks = new List<LinkWidget>();

        protected SlotManager leftSlots;
        protected SlotManager rightSlots;
        protected SlotManager topSlots;
        protected SlotManager bottomSlots;

        private bool generateCodeForItem = true;

        internal bool GenerateCodeForItem
        {
            get
            {
                return generateCodeForItem;
            }

            set
            {
                generateCodeForItem = value;
            }
        }

    }
}
